/**
 *  用法见:
 *  test/src/math_utils.test.js
 */
let MathUtils =  {
    /**
     * 返回 [min,max] 之间的数
     * @param min
     * @param max
     * @return {*}
     */
    random(min, max) {
        if (max == null) {
            max = min;
            min = 0;
        }
        return min + Math.floor(Math.random() * (max - min + 1));
    },

    /**
     * 将 x 限制在 [minVal,maxVal] 之间
     * @param x
     * @param minVal
     * @param maxVal
     * @return {number}
     */
    clamp(x, minVal, maxVal) {
        return Math.min(Math.max(x, minVal), maxVal);
    },


    /**
     * 和 glsl smoothstep 方法一样
     * @param edge0  下界
     * @param edge1  上界
     * @param x 用于插值的值
     * @return {number} 范围为 [0,1]
     */
    smoothstep(edge0, edge1, x) {
        let t = this.clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
        return t * t * (3.0 - 2.0 * t);
    },

    /**
     * 和  glsl step 方法一样
     * @param edge
     * @param x
     * @return {number}  返回  0 或者 1
     */
    step(edge, x) {
        if (x < edge) {
            return 0;
        } else {
            return 1;
        }
    },

    /**
     * 将 x 在 [a,b]区域, 映射到 在 [c,d] 的 x1
     * @param x
     * @param a
     * @param b
     * @param c
     * @param d
     * @return x1
     */
    mapValue(x, a, b, c, d) {
        return (x - a) * (d - c) / (b - a) + c;
    },

    /**
     * 线性插值
     * @param start
     * @param end
     * @param x
     * @return {number}
     */
    linear_interpolation(start, end, x) {
        return start * (1 - x) + end * x;
    },

    // /**
    //  * 随机种子.
    //  * 可以生成确定性的随机数
    //  *  let func1 = seed(1234);
    //  *  let value = func1(); //  value 的值是确定性的.
    //  *
    //  * @param s
    //  * @return {function(): number}
    //  */
    // seed(s) {
    //     let m_w = s;
    //     let m_z = 987654321;
    //     let mask = 0xffffffff;
    //     return function () {
    //         m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
    //         m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;
    //
    //         let result = ((m_z << 16) + m_w) & mask;
    //         result /= 4294967296;
    //
    //         return result + 0.5;
    //     };
    // }


    /**
     * 直线相交 求交点
     * https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
     * x1 y1 x2 y2 表示第1条直线
     * x3 y3 x4 y4 表示第2条直线
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @param x4
     * @param y4
     * @return {*}  如果存在交点,就返回交点坐标,不存在就返回 null
     */
    lineAndLine(x1,y1,x2,y2,x3,y3,x4,y4){
        let params = this._lineAndLineParamters(x1,y1,x2,y2,x3,y3,x4,y4);
        if(params){
            let x = x1 + params.t * (x2 - x1);
            let y = y1 + params.t * (y2 - y1);
            return {x:x,y:y};
        }
        return null;
    },

    /**
     * 判断直线是否相交?
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @param x4
     * @param y4
     * @return {boolean}
     */
    isLineAndLineIntersection(x1,y1,x2,y2,x3,y3,x4,y4){
        return !this.isLineAndLineParallel(x1,y1,x2,y2,x3,y3,x4,y4);
    },

    /**
     * 两直线是否平行?
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @param x4
     * @param y4
     * @return {boolean}
     */
    isLineAndLineParallel(x1,y1,x2,y2,x3,y3,x4,y4){
        let det = (x1 - x2 ) * (y3 - y4) - (y1 - y2) * (x3 - x4);
        return det === 0;
    },

    /**
     * 线段与线段的交点
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @param x4
     * @param y4
     * @return {object | null}
     */
    segmentAndSegment(x1,y1,x2,y2,x3,y3,x4,y4){

        let params = this._lineAndLineParamters(x1,y1,x2,y2,x3,y3,x4,y4);
        let t,u;
        if(!params){
            return null;
        }

        t = params.t;
        u = params.u;
        if( t >=0 && t <= 1 && u >=0 && u <= 1){
            let x = x1 + t * (x2 - x1);
            let y = y1 + t * (y2 - y1);
            return {x:x,y:y};
        }else{
            return null;
        }
    },

    /**
     * 射线与线段的交点
     *  x1 y1 表示射线的原点
     *  dirX dirY 表示射线的方向(不要求是单位向量)
     * @param x1
     * @param y1
     * @param dirX
     * @param dirY
     * @param x3
     * @param y3
     * @param x4
     * @param y4
     * @return {object | null}
     */
    rayAndSeg(x1,y1,dirX,dirY,x3,y3,x4,y4){
        let x2 = x1 + dirX;
        let y2 = y1 + dirY;
        let params = this._lineAndLineParamters(x1,y1,x2,y2,x3,y3,x4,y4);
        let t,u;
        if(!params){
            return null;
        }

        t = params.t;
        u = params.u;
        if(t >= 0 && u >=0 && u <= 1){
            let x = x1 + t * (x2 - x1);
            let y = y1 + t * (y2 - y1);
            return {x:x,y:y};
        }else{
            return null;
        }
    },

    /**
     * x1,y1,x2,y2 第1条直线上的两个点
     * x3,y3,x4,y4 第2条直线上的两个点
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @param x4
     * @param y4
     * @return  {*}
     *        t表示在第1条直线上的参数,u表示在第2条直线上的参数.
     *        交点为:
     *          x = x1 + t * (x2 - x1)
     *          y = y1 + t * (y2 - y1)
     *        或:
     *          x = x3 + u * (x4 - x3)
     *          y = y3 + u * (y4 - y3)
     * @private
     */
    _lineAndLineParamters(x1,y1,x2,y2,x3,y3,x4,y4){
        let det =  (x1 - x2 ) * (y3 - y4) - (y1 - y2) * (x3 - x4);
        if(det === 0){
            // 平行或共线
            return null;
        }

        let t = ( (x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / det;
        let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / det;
        return {t:t,u:u};
    },

    /**
     * 求p在 AB 上的投影的点 D   在 AB 参数方程上的参数t
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param px
     * @param py
     * @param clampToLine  true 表示 t 限制在[0,1],即 AB 表示为线段
     * @return {*}
     */
    closestPointToPointParameter(x1,y1,x2,y2,px,py,clampToLine){

        let ab_x = x2 - x1;
        let ab_y = y2 - y1;

        let ap_x = px - x1;
        let ap_y = py - y1;

        let ab_lenSq = ab_x * ab_x + ab_y * ab_y;
        let ab_dot_ap = ab_x * ap_x + ab_y * ap_y;
        let t = ab_dot_ap / ab_lenSq;
        if(clampToLine){
            return this.clamp(t,0,1);
        }
        return t;
    },

    /**
     * 求点p 到 直线 AB的最近点的坐标
     * clampToLine 为false时,所求点为 p 在AB上的投影点.
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param px
     * @param py
     * @param clampToLine
     * @return {{x: *, y: *}}
     */
    closestPointToPoint(x1,y1,x2,y2,px,py,clampToLine){
        let t = this.closestPointToPointParameter(x1,y1,x2,y2,px,py,clampToLine);
        let x = x1 + t * (x2 - x1);
        let y = y1 + t * (y2 - y1);
        return {x:x,y:y};
    },

    /**
     * 求直线的法向量. (右手定则)
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param normalized
     * @return {{x: number, y: number}}
     */
    lineNormalDir(x1,y1,x2,y2,normalized = false){
        let nx = y1 - y2;
        let ny = x2 - x1;
        if(normalized){
            let len = Math.sqrt(nx * nx + ny * ny);
            return {x:nx/len,y:ny/len};
        }else{
            return {x:nx,y:ny};
        }
    },

    /**
     * 点是否在直线的正方向.
     *  注意:点在直线上的时,返回false.
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param px
     * @param py
     * @return {boolean}
     */
    isPointAtFrontOfLine(x1,y1,x2,y2,px,py){
        let normalDir = this.lineNormalDir(x1,y1,x2,y2);
        let ap_x = px - x1;
        let ap_y = py - y1;
        // dot
        if(normalDir.x * ap_x + normalDir.y * ap_y > 0){
            return true;
        }
        return false;
    },

    // 直线与圆相交?  略
    // 直线与矩形相交?
    // 直线与三角形相交?
    // 点是否在三角形内?
    // 直线与多边形相交?
    //
    /**
     * 返回三角形 重心坐标
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @return {{x: number, y: number}}
     */
    getTriangleBarycentric(x1,y1,x2,y2,x3,y3){
        let x = (x1 + x2 + x3) / 3;
        let y = (y1 + y2 + y3) / 3;
        return {x:x,y:y}
    },


    /**
     * 点是否在三角形内
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @param px
     * @param py
     * @return {boolean}
     */
    isPointInTriangle(x1,y1,x2,y2,x3,y3,px,py){

        // 使用重心坐标方式判断
        // 求出 i , j
        // 判断 i >0 && j > 0 && i + j < 1
        /**
         * P.x - A.x = (C.x - A.x) * i + (B.x - A.x) * j
         * P.y - A.y = (C.y - A.y) * i + (B.y - A.y) * j
         * 解2元1次方程
         */
        let a = x2 - x1;
        let b = x3 - x1;
        let c = px - x1;
        let d = y2 - y1;
        let e = y3 - y1;
        let m = py - y1;
        let ij = this.solve_equat2_1(a,b,c,d,e,m);
        if(ij && ij.x > 0 && ij.y > 0 && ij.x + ij.y < 1){
            return true;
        }
        return false;
    },

    /**
     * 判断点是否在三角形内
     *  利用点和每个边的关系
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @param px
     * @param py
     * @return {*|boolean}
     */
    isPointInTriangle2(x1,y1,x2,y2,x3,y3,px,py){
        if(!this.isCCWTriangle(x1,y1,x2,y2,x3,y3)){
            let tmpX = x2;
            let tmpY = y2;
            x2 = x3;
            y2 = y3;
            x3 = tmpX;
            y3 = tmpY;
        }

        // p 在 ab bc ca的正方向
        let p_ab = this.isPointAtFrontOfLine(x1,y1,x2,y2,px,py);
        let p_bc = this.isPointAtFrontOfLine(x2,y2,x3,y3,px,py);
        let p_ca = this.isPointAtFrontOfLine(x3,y3,x1,y1,px,py);
        return p_ab && p_bc && p_ca;
    },

    /**
     * 判断三角形是不是 逆时针环绕
     * A (x1,y1)
     * B (x2,y2)
     * C (x3,y3)
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
     * @return {boolean}
     */
    isCCWTriangle(x1,y1,x2,y2,x3,y3){
        let ab_x = x2 - x1;
        let ab_y = y2 - y1;

        let ac_x = x3 - x1;
        let ac_y = y3 - y1;

        let cross = ab_x * ac_y - ab_y * ac_x;
        if(cross > 0){
            return true;
        }
        return false;
    },

    /**
     *  求解2元1次方程
     *  ax + by = c;
     *  dx + ey = m
     *
     *  x = ( c * e - b * m ) / (e * a - b * d)
     *  y = -(d * c - a * m ) / (e * a - b * d)
     */
    solve_equat2_1(a,b,c,d,e,m){
        let det = e * a - b * d;
        if(det === 0){
            // 无数个解. 认为无解
            return null;
        }

        let x = (c * e - b * m) / det;
        let y = -(d * c - a * m) / det;
        return {x:x,y:y};
    },


    /**
     * Ank  = n * (n - 1) * (n - 2) * ... * (n - k + 1);
     * @param n
     * @param k
     * @return {number}
     */
    ank(n,k){
        let sum = 1;
        for(let j = 0, i = n; j < k; ++j,--i){
            sum *= i;
        }
        return sum;
    },


    /**
     * Cnk = Ank / k!
     * @param n
     * @param k
     * @return {number}
     */
    cnk(n,k){
        return this.ank(n,k) / this.ank(k,k);
    }



    // TODO:多边形的三角剖分
    // TODO:2d bsp划分
    // TODO:noise 1
    // TODO:noise 2
};
export  default MathUtils;
